package com.db.stexchem.server;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.db.stexchem.message.ExecutionReport;
import com.db.stexchem.serialization.ProtoBufSerializerImpl;
import com.db.stexchem.serialization.Serializer;

public class StockExchangeService extends Thread {
  
  private final Logger logger = LoggerFactory
      .getLogger(StockExchangeService.class);
  private final ServerSocket serverSocket;
  private final Set<String> stocks = new HashSet<>();
  private final Map<String, String> passwordStorage = new HashMap<>();
  private final ConcurrentHashMap<String, ClientSession> activeSessions = new ConcurrentHashMap<>();
  private final ConcurrentHashMap<String, Collection<ExecutionReport>> waitingNotifications = new ConcurrentHashMap<>();
  private final OrderService orderService;
  private final LoginService loginService = new LoginService(passwordStorage,
      activeSessions);
  private final NotificationService notificationService = new NotificationService(
      waitingNotifications, activeSessions);
  private Serializer serializer = new ProtoBufSerializerImpl();

  public StockExchangeService(String propertiesFile)
      throws IOException {
    // create and load default properties
    Properties properties = new Properties();
    try (FileInputStream in = new FileInputStream(propertiesFile)) {
      logger.trace("trying to load properties from '{}' file ...",
          propertiesFile);
      properties.load(in);
      logger.trace("loaded properties");
    }
    
    Properties passwordStorageProperties = new Properties();
    String passwordStorageFile = properties.getProperty(
        PASSWORD_STORAGE, DEFAULT_PASSWORD_STORAGE);
    try (FileInputStream in = new FileInputStream(passwordStorageFile)) {
      logger.trace("trying to load password storage from '{}' file ...",
          passwordStorageFile);
      passwordStorageProperties.load(in);
      logger.trace("loaded password storage");
    }
    
    for (Entry<Object, Object> entry : passwordStorageProperties.entrySet()) {
      String login = (String) entry.getKey();
      String password = (String) entry.getValue();
      passwordStorage.put(login, password);
    }
    
    String stocksListFile = properties.getProperty(STOCKS_LIST,
        DEFAULT_STOCKS_LIST);
    try (BufferedReader in = new BufferedReader(new FileReader(stocksListFile))) {
      logger.trace("trying to read stocks list from '{}' file ...",
          stocksListFile);
      String stock;
      while ((stock = in.readLine()) != null) {
        stocks.add(stock);
      }
      logger.trace("read stocks list");
    }
    orderService = new OrderService(stocks);
    
    int port = Integer.parseInt(properties.getProperty(PORT, DEFAULT_PORT));
    int backlog = Integer.parseInt(properties.getProperty(BACKLOG, DEFAULT_BACKLOG));
    
    logger.trace("trying to create server socket on {} port with {} ...",
        port, backlog);
    serverSocket = new ServerSocket(port, backlog);
    logger.trace("created server socket");
  }
  
  @Override
  public void run() {
    try {
      logger.trace("starting stock exchange service ...");
      for (;;) {
        new Thread(new ClientSession(serverSocket.accept(), loginService,
            orderService, notificationService, serializer)).start();
      }
    } catch (IOException e) {
      logger.error("failed to accept socket: {}", e.getMessage());
    }
  }
  
  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(StockExchangeService.class);
    
    Set<String> arguments = new HashSet<String>();
    for (String string : args) {
      arguments.add(string);
    }
    
    if (arguments.contains("-h") || arguments.contains("--help")) {
      System.out.println("usage: java " + StockExchangeService.class.getName()
          + " [<ini-file>]");
      System.out.println("where <ini-file> - path to file (by default will be used '"
          + DEFAULT_INI_FILE +"') with <key>=<value> structure:");
      System.out.println("\t" + PASSWORD_STORAGE +"=<file with structure <login>=<password>> (default is '" + DEFAULT_PASSWORD_STORAGE + "')");
      System.out.println("\t" + STOCKS_LIST +"=<file with stock name on each line> (default is '" + DEFAULT_STOCKS_LIST + "')");
      System.out.println("\t" + PORT + "=<int number of server port> (default is " + DEFAULT_PORT + ")");
      System.out.println("\t" + BACKLOG + "=<int backlog of incoming connections> (default is " + DEFAULT_BACKLOG +")");
      return;
    }

    String iniFile;

    if (args.length > 0) {
      iniFile = args[0];
    } else {
      iniFile = DEFAULT_INI_FILE;
      logger.info("loading default settings '{}' ...", iniFile);
    }
        
    StockExchangeService stockExchangeService;
    try {
      stockExchangeService = new StockExchangeService(iniFile);
    } catch (IOException e) {
      logger.error("failed to create StockExchangeService: {}", e.getLocalizedMessage());
      return;
    }
    
    stockExchangeService.start();
  }
  
  private static final String DEFAULT_INI_FILE = "server.ini";
  private static final String PASSWORD_STORAGE = "password-storage-file";
  private static final String DEFAULT_PASSWORD_STORAGE = "password-storage.ini";
  private static final String STOCKS_LIST = "stocks-lists-file";
  private static final String DEFAULT_STOCKS_LIST = "stocks-lists.txt";
  private static final String PORT = "port";
  private static final String DEFAULT_PORT = "2004";
  private static final String BACKLOG = "backlog";
  private static final String DEFAULT_BACKLOG = "10";
}
